package com.kakarote.authorization.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.kakarote.authorization.common.AuthorizationCodeEnum;
import com.kakarote.authorization.common.AuthorizationConst;
import com.kakarote.authorization.common.LoginType;
import com.kakarote.authorization.entity.AuthenticatedUser;
import com.kakarote.authorization.entity.AuthorizationUser;
import com.kakarote.authorization.entity.VO.CompanyDetails;
import com.kakarote.authorization.entity.VO.CompanyDetailsList;
import com.kakarote.authorization.entity.VO.LoginVO;
import com.kakarote.authorization.service.AdminUserService;
import com.kakarote.authorization.service.AuthService;
import com.kakarote.authorization.service.UserAuthenticationService;
import com.kakarote.core.common.Const;
import com.kakarote.core.common.Result;
import com.kakarote.core.common.cache.AdminCacheKey;
import com.kakarote.core.common.cache.CrmCacheKey;
import com.kakarote.core.common.cache.ScrmCacheKey;
import com.kakarote.core.common.enums.SystemCodeEnum;
import com.kakarote.core.entity.UserInfo;
import com.kakarote.core.exception.CrmException;
import com.kakarote.core.feign.admin.entity.AdminConfig;
import com.kakarote.core.feign.admin.entity.AdminUserInfo;
import com.kakarote.core.feign.admin.service.AdminService;
import com.kakarote.core.redis.Redis;
import com.kakarote.core.utils.HttpUtils;
import com.kakarote.core.utils.UserUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author Administrator
 */
@Service
@Slf4j
public class AuthServiceImpl implements AuthService {

    @Autowired
    private AdminUserService adminUserService;

    @Autowired
    private List<UserAuthenticationService> userAuthenticationServiceList;

    @Autowired
    private AdminService adminService;

    @Autowired
    private Redis redis;

    @Value("${sso.unify_auth_url.authorization_code}")
    private String authorizationCodeUrl;

    @Value("${sso.unify_auth_url.access_token}")
    private String accessTokenUrl;

    @Value("${sso.unify_auth_url.open_id}")
    private String openIdUrl;

    @Value("${sso.unify_auth_url.user_info}")
    private String userInfoUrl;

    @Value("${sso.oauth_consumer_key}")
    private String oauthConsumerKey;

    @Value("${sso.oauth_consumer_key_app}")
    private String oauthConsumerKeyApp;

    @Value("${sso.client_id}")
    private String clientId;

    @Value("${sso.client_id_app}")
    private String clientIdApp;

    @Value("${sso.client_secret}")
    private String clientSecret;

    @Value("${hrm.redirect_uri}")
    private String redirectUri;

    @Value("${hrm.redirect_uri_app}")
    private String redirectUriApp;

    /**
     * 登录方法的处理
     *
     * @param userInfo 用户对象
     * @return Result
     */
    @Override
    public Result<LoginVO> login(UserInfo userInfo) {
        String token = IdUtil.simpleUUID();
        if (userInfo.getStatus() == 0) {
            throw new CrmException(AuthorizationCodeEnum.AUTHORIZATION_USER_DISABLE_ERROR);
        }
        // 解决切面里面无法找到当前登录企业的问题
        UserUtil.setUser(new UserInfo().setUserId(userInfo.getUserId()));
        //人资系统
        int hrmIndex = 5;
        if (ObjectUtil.equal(hrmIndex, userInfo.getType())) {
            AdminConfig hrm = adminService.queryFirstConfigByName("hrm").getData();
            if (ObjectUtil.isNull(hrm) || ObjectUtil.equal(0, hrm.getStatus())) {
                throw new CrmException(AuthorizationCodeEnum.AUTHORIZATION_LOGIN_MODULE_NOT_EXIST, "人力资源管理系统");
            }
        }
        // 财务系统
        int financeIndex = 6;
        if (ObjectUtil.equal(financeIndex, userInfo.getType())) {
            AdminConfig finance = adminService.queryFirstConfigByName("finance").getData();
            if (ObjectUtil.isNull(finance) || ObjectUtil.equal(0, finance.getStatus())) {
                throw new CrmException(AuthorizationCodeEnum.AUTHORIZATION_LOGIN_MODULE_NOT_EXIST, "财务管理系统");
            }
        }
        userInfo.setRoles(adminUserService.queryUserRoleIds(userInfo.getUserId()).getData());
        UserUtil.userToken(token, userInfo, userInfo.getType());
        // 未激活状态码
        int userStatus = 2;
        if (userInfo.getStatus() == userStatus) {
            UserUtil.setUser(userInfo);
//            adminUserService.setUserStatus(AdminUserStatusBO.builder().status(1).ids(Collections.singletonList(userInfo.getUserId())).build());
        }
        if (ObjectUtil.equal(userInfo.getType(), 2)) {
            redis.setex(Const.USER_LOGIN_LASTED_TIME_MOBILE + token, 3600 * 24 * 7, LocalDateTime.now());
        } else {
            adminUserService.updateAdminUserLoginTime(userInfo.getUsername());
        }
        redis.del(AdminCacheKey.USER_AUTH_CACHE_KET + userInfo.getUserId());
        redis.del(CrmCacheKey.CRM_AUTH_USER_CACHE_KEY + userInfo.getUserId());
        redis.del(ScrmCacheKey.SCRM_AUTH_USER_CACHE_KEY + userInfo.getUserId());
        redis.del(Const.NO_AUTH_MENU_CACHE_KEY + userInfo.getUserId());

        return Result.ok(new LoginVO().setAdminToken(token));
    }

    /**
     * 登录方法的处理
     *
     * @param user 用户对象
     * @return Result
     */
    @Override
    public Result<?> doLogin(AuthorizationUser user) {
        if (user.getLoginType() == null) {
            user.setLoginType(LoginType.PASSWORD);
        }
        boolean isPassWordLogin = user.getLoginType().equals(LoginType.PASSWORD) || user.getLoginType().equals(LoginType.SMS_CODE);
        if (isPassWordLogin) {
            String key = AdminCacheKey.PASSWORD_ERROR_CACHE_KEY + user.getUsername().trim();
            Integer errorNum = redis.get(key);
            //查企业安全设置信息
            Result<JSONObject> securitySetting = adminUserService.queryByCompanyIdType(1);
            JSONObject data = securitySetting.getData();
            // 密码输入错误次数上限
            if (errorNum != null && errorNum >= data.getInteger("pwdErrorNum")) {
                int second = Optional.ofNullable(redis.ttl(key)).orElse(0L).intValue();
                if (second > 0) {
                    String errorTimeDesc = this.getErrorTimeDesc(second);
                    return Result.error(AuthorizationCodeEnum.AUTHORIZATION_LOGIN_PASSWORD_TO_MANY_ERROR, "密码错误次数过多，请在" + errorTimeDesc + "后重试！");
                }
            }
        }

        //判断登录类型是否支持
        Optional<UserAuthenticationService> authenticationServiceOptional = userAuthenticationServiceList.stream().filter(service -> service.supports(user.getLoginType())).findFirst();
        if (!authenticationServiceOptional.isPresent()) {
            return Result.error(AuthorizationCodeEnum.AUTHORIZATION_MONTH_ERROR);
        }
        //登录参数验证
        UserAuthenticationService authenticationService = authenticationServiceOptional.get();
        if (!authenticationService.verifyParameter(user)) {
            if (isPassWordLogin) {
                return Result.error(AuthorizationCodeEnum.AUTHORIZATION_PASSWORD_REQUIRED);
            } else {
                return Result.error(AuthorizationCodeEnum.AUTHORIZATION_LOGIN_ERR);
            }
        }

        //用户是否存在
        AuthenticatedUser authenticatedUser = authenticationService.loadUser(user);
        List<AdminUserInfo> userInfoList = authenticatedUser.getUserInfoList();
        if (userInfoList.isEmpty()) {
            return Result.error(AuthorizationCodeEnum.AUTHORIZATION_USER_DOES_NOT_EXIST);
        }

        userInfoList.removeIf(userInfo -> !authenticationService.isNormalUser(userInfo, user));

        if (userInfoList.isEmpty()) {
            return this.handleLoginPassWordToManyError(user.getUsername().trim());
        } else if (userInfoList.size() > 1) {
            String tempToken = IdUtil.simpleUUID();
            redis.setex(tempToken, AuthorizationConst.ENTRY_INHIBITED_TIME, authenticatedUser);
            CompanyDetailsList companyDetailsList = new CompanyDetailsList();
            companyDetailsList.setCompanyList(userInfoList.stream().map(userInfo -> new CompanyDetails( userInfo.getCompanyName(), user.getLoginType().getType(), user.getType(),tempToken)).collect(Collectors.toList()));
            return Result.ok(companyDetailsList);
        }
        UserInfo userInfo = BeanUtil.copyProperties(userInfoList.get(0), UserInfo.class);
        userInfo.setType(user.getType());
        userInfo.setLoginType(user.getLoginType().getType());
        return login(userInfo);

    }

    /**
     * 预登录
     *
     * @param user 用户对象
     * @return Result
     */
    @Override
    public Result<?> wxLogin(AuthorizationUser user) {
        if (user.getLoginType() == null || user.getLoginType() != LoginType.WX_WORK) {
            return Result.error(AuthorizationCodeEnum.AUTHORIZATION_MONTH_ERROR);
        }
        Optional<UserAuthenticationService> authenticationServiceOptional = userAuthenticationServiceList.stream().filter(service -> service.supports(LoginType.WX_WORK)).findFirst();
        if (!authenticationServiceOptional.isPresent()) {
            return Result.error(SystemCodeEnum.SYSTEM_NO_VALID);
        }
        //验证参数
        UserAuthenticationService authenticationService = authenticationServiceOptional.get();
        if (!authenticationService.verifyParameter(user)) {
            return Result.error(AuthorizationCodeEnum.AUTHORIZATION_LOGIN_ERR);
        }
        //用户是否存在
        AuthenticatedUser authenticatedUser = authenticationService.loadUser(user);
        List<AdminUserInfo> userInfoList = authenticatedUser.getUserInfoList();
        if (userInfoList.isEmpty()) {
            return Result.error(AuthorizationCodeEnum.AUTHORIZATION_USER_DOES_NOT_EXIST);
        }
        UserInfo userInfo = BeanUtil.copyProperties(userInfoList.get(0), UserInfo.class);
        userInfo.setType(user.getType());
        userInfo.setLoginType(user.getLoginType().getType());
        return login(userInfo);

    }


    /**
     * 密码失败次数处理
     *
     * @param userName 用户名
     * @return Result
     **/
    private Result<String> handleLoginPassWordToManyError(String userName) {
        String key = AdminCacheKey.PASSWORD_ERROR_CACHE_KEY + userName;
        Integer errorNum = redis.get(key);
        if (errorNum == null) {
            errorNum = 0;
        }
        redis.setex(AdminCacheKey.PASSWORD_ERROR_CACHE_KEY + userName, AuthorizationConst.ENTRY_INHIBITED_TIME, errorNum + 1);
        return Result.error(AuthorizationCodeEnum.AUTHORIZATION_LOGIN_NO_USER);
    }

    /**
     * 权限验证
     *
     * @param authentication token
     * @param url            url
     * @param method         method
     * @return Result
     */
    @Override
    public Result<String> permission(String authentication, String url, String method) {
        UserInfo userInfo = redis.get(authentication);
        if (userInfo == null) {
            throw new CrmException(SystemCodeEnum.SYSTEM_NOT_LOGIN);
        }
        Long userId = userInfo.getUserId();
        String key = Const.NO_AUTH_MENU_CACHE_KEY + userId;
        List<String> noAuthMenuUrls = redis.get(key);
        if (noAuthMenuUrls == null) {
            noAuthMenuUrls = adminUserService.queryNoAuthMenu(userId).getData();
            redis.setex(key, 1800, noAuthMenuUrls);
        }
        noAuthMenuUrls.remove("/hrmEmployee/addExperience");
        noAuthMenuUrls.remove("/hrmEmployee/setEduExperience");
        noAuthMenuUrls.remove("/hrmEmployee/deleteEduExperience/${id}");
        noAuthMenuUrls.remove("/hrmEmployee/addWorkExperience");
        noAuthMenuUrls.remove("/hrmEmployee/setWorkExperience");
        noAuthMenuUrls.remove("/hrmEmployee/deleteWorkExperience/${id}");
        noAuthMenuUrls.remove("/hrmEmployee/addCertificate");
        noAuthMenuUrls.remove("/hrmEmployee/setCertificate");
        noAuthMenuUrls.remove("/hrmEmployee/deleteCertificate/${id}");
        noAuthMenuUrls.remove("/hrmEmployee/addTechnical");
        noAuthMenuUrls.remove("/hrmEmployee/setTechnical");
        noAuthMenuUrls.remove("/hrmEmployee/deleteTechnical/${id}");
        noAuthMenuUrls.remove("/hrmEmployee/addSpecialWorker");
        noAuthMenuUrls.remove("/hrmEmployee/setSpecialWorker");
        noAuthMenuUrls.remove("/hrmEmployee/deleteSpecialWorker/${id}");
        noAuthMenuUrls.remove("/hrmEmployee/addTrainingExperience");
        noAuthMenuUrls.remove("/hrmEmployee/setTrainingExperience");
        noAuthMenuUrls.remove("/hrmEmployee/deleteTrainingExperience/${id}");
        noAuthMenuUrls.remove("/hrmEmployee/queryContactsAddField");
        noAuthMenuUrls.remove("/hrmEmployee/addContacts");
        noAuthMenuUrls.remove("/hrmEmployee/setContacts");
        noAuthMenuUrls.remove("/hrmEmployee/deleteContacts/${id}");
        noAuthMenuUrls.remove("/hrmEmployee/updateInformation");
        noAuthMenuUrls.remove("/hrmEmployee/updateCommunication");
        boolean permission = isHasPermission(noAuthMenuUrls, url);
        return permission ? Result.ok() : Result.noAuth();
    }

    /**
     * 退出登陆
     *
     * @param authentication token
     */
    @Override
    public void logout(String authentication) {
        Object data = redis.get(authentication);
        if (data instanceof UserInfo) {
            UserInfo userInfo = (UserInfo) data;
            Optional<UserAuthenticationService> serviceOptional = userAuthenticationServiceList.stream().filter(service -> service.supports(LoginType.PASSWORD)).findFirst();
            serviceOptional.ifPresent(service -> service.logout(userInfo));
            UserUtil.userLogOut(userInfo, authentication);
        }

    }

    /**
     * 获取手机端二维码登录地址
     *
     * @param request request
     * @return url
     */
    @Override
    public String getLoginQrCode(HttpServletRequest request) {
        //由sentinel进行登录次数拦截，此处不做限制
        String token = IdUtil.simpleUUID();
        //设置有效期为分钟
        redis.setex(token, 180, "");
        return token;
    }

    /**
     * 获取当前是否登录成功
     *
     * @param token:key
     * @return 登录VO
     */
    @Override
    public LoginVO getLoginQrInfo(String token) {
        String data = redis.get(token);
        if (null == data) {
            throw new CrmException(AuthorizationCodeEnum.QRCODE_INFO_IS_NULL);
        } else if (data.isEmpty()) {
            throw new CrmException(AuthorizationCodeEnum.QRCODE_DID_NOT_CONFIRM);
        }
        return new LoginVO().setAdminToken(data);
    }

    /**
     * 设置登录用户信息
     *
     * @param token 当前手机端登录token
     */
    @Override
    public void setQrInfo(String token) {
        UserInfo user = UserUtil.getUser();
        String uuid = IdUtil.simpleUUID();
        UserUtil.userToken(uuid, user, 1);
        redis.setex(token, 60, uuid);
    }

    /**
     * 选择企业后重新登录
     *
     * @param companyDetails 公司信息
     * @return result
     */
    @Override
    public Result<?> reLogin(CompanyDetails companyDetails) {
        if (!redis.exists(companyDetails.getTempToken())) {
            return Result.error(AuthorizationCodeEnum.AUTHORIZATION_COMPANY_NOT_EXIST);
        }
        AuthenticatedUser authenticatedUser = redis.get(companyDetails.getTempToken());
        List<AdminUserInfo> infoList = authenticatedUser.getUserInfoList();
        Optional<AdminUserInfo> adminUserInfoOptional = infoList.stream()
                .findFirst();
        if (!adminUserInfoOptional.isPresent()) {
            return Result.error(AuthorizationCodeEnum.AUTHORIZATION_COMPANY_NOT_EXIST);
        }
        UserInfo userInfo = BeanUtil.copyProperties(adminUserInfoOptional.get(), UserInfo.class);
        userInfo.setType(companyDetails.getType());
        userInfo.setLoginType(companyDetails.getLoginType());
        return login(userInfo);
    }

    /**
     * 判断有无权限访问
     *
     * @param noAuthMenuUrls 无权限的url列表
     * @param url            当前url
     * @return boolean
     * @date 2020/8/21 13:35
     **/
    private boolean isHasPermission(List<String> noAuthMenuUrls, String url) {
        //用户信息丢失 | 错误
        if (noAuthMenuUrls == null) {
            return false;
        }
        //管理员
        if (noAuthMenuUrls.isEmpty()) {
            return true;
        }
        //没有任何权限
        String equesAuth = "/*/**";
        if (noAuthMenuUrls.size() == 1 && equesAuth.equals(noAuthMenuUrls.get(0))) {
            return false;
        }
        boolean permission = true;
        for (String noAuthMenuUrl : noAuthMenuUrls) {
            if (noAuthMenuUrl.contains("*")) {
                if (noAuthMenuUrl.contains(",")) {
                    boolean isNoAuth = false;
                    String splitIndex = ",";
                    for (String noAuthUrl : noAuthMenuUrl.split(splitIndex)) {
                        if (url.startsWith(noAuthUrl.replace("*", ""))) {
                            isNoAuth = true;
                            break;
                        }
                    }
                    if (isNoAuth) {
                        permission = false;
                        break;
                    }
                } else {
                    if (url.startsWith(noAuthMenuUrl.replace("*", ""))) {
                        permission = false;
                        break;
                    }
                }
            } else {
                if (noAuthMenuUrl.contains(",")) {
                    if (Arrays.asList(noAuthMenuUrl.split(",")).contains(url)) {
                        permission = false;
                        break;
                    }
                } else {
                    if (noAuthMenuUrl.equals(url)) {
                        permission = false;
                        break;
                    }
                }
            }
        }
        return permission;
    }


    /**
     * 时间描述
     *
     * @param second 秒数
     * @return java.lang.String
     * @date 2020/11/9 16:57
     **/
    private String getErrorTimeDesc(Integer second) {
        String errorTimeDesc;
        // 1-5分钟的秒数
        int five = 300;
        int four = 240;
        int three = 180;
        int two = 120;
        int one = 60;
        if (Arrays.asList(five, four, three, two, one).contains(second)) {
            errorTimeDesc = second / 60 + "分";
        } else if (second < one) {
            errorTimeDesc = second + "秒";
        } else {
            errorTimeDesc = second / 60 + "分" + second % 60 + "秒";
        }
        return errorTimeDesc;
    }

    /**
     * 单点鉴权
     *
     * @return Result
     */
    @Override
    public Result<?> doSsoAuth(String platform) {
        Map<String, String> map = new HashMap();
        map.put("response_type", "code");
        if(AuthorizationConst.APP.equals(platform)){
            map.put("redirect_uri", redirectUriApp);
            map.put("client_id", clientIdApp);
        } else {
            map.put("redirect_uri", redirectUri);
            map.put("client_id", clientId);
        }
        map.put("state", "0");
        log.info("单点鉴权请求地址：{}", authorizationCodeUrl);
        log.info("单点鉴权请求参数：{}", JSONObject.toJSONString(map));
        String result = HttpUtils.createGetHttp(authorizationCodeUrl, map);
        log.info("单点鉴权请求响应：{}", result);
        return Result.ok();
    }


    /**
     * 单点登录处理
     *
     * @param code 统一身份认证Authorization Code
     * @param platform 来源平台pc，app
     * @return Result
     */
    @Override
    public Result<?> doSsoLogin(String code, String platform) {
        //统一身份认证系统与hr系统授信
        Map<String, String> map = new HashMap();
        map.put("grant_type", "authorization_code");
        if(AuthorizationConst.APP.equals(platform)){
            map.put("redirect_uri", redirectUriApp);
            map.put("client_id", clientIdApp);
        } else {
            map.put("redirect_uri", redirectUri);
            map.put("client_id", clientId);
        }
        map.put("client_secret", clientSecret);
        map.put("code", code);
        log.info("获取accessToken请求地址：{}", accessTokenUrl);
        log.info("获取accessToken请求参数：{}", JSONObject.toJSONString(map));
        String accessTokeResult = HttpUtils.createGetHttp(accessTokenUrl, map);
        log.info("获取accessToken请求响应：{}", accessTokeResult);
        if(StringUtils.isEmpty(accessTokeResult)){
            return Result.error(SystemCodeEnum.SYSTEM_ERROR);
        }
        JSONObject accessTokeJsonObject = JSONObject.parseObject(accessTokeResult);
        String ret = accessTokeJsonObject.getString("ret");
        if(!"0".equals(ret)){
            return Result.error(SystemCodeEnum.SYSTEM_ERROR);
        }
        String accessToken = accessTokeJsonObject.getString("access_token");
        Map<String, String> m2 = new HashMap();
        m2.put("access_token", accessToken);
        log.info("获取openId请求地址：{}", openIdUrl);
        log.info("获取openId请求参数：{}", JSONObject.toJSONString(m2));
        String openIdResult = HttpUtils.createGetHttp(openIdUrl, m2);
        log.info("获取openId请求响应：{}", openIdResult);
        if(StringUtils.isEmpty(openIdResult)){
            return Result.error(SystemCodeEnum.SYSTEM_ERROR);
        }
        JSONObject openIdJsonObject = JSONObject.parseObject(openIdResult);
        String openIdRet = openIdJsonObject.getString("ret");
        if(!"0".equals(openIdRet)){
            return Result.error(SystemCodeEnum.SYSTEM_ERROR);
        }
        String openid = openIdJsonObject.getString("openid");
        String username = openIdJsonObject.getString("username");
        Map<String, String> m3 = new HashMap();
        m3.put("access_token", accessToken);
        m3.put("openid", openid);
        if(AuthorizationConst.APP.equals(platform)){
            m3.put("oauth_consumer_key", oauthConsumerKeyApp);
        } else {
            m3.put("oauth_consumer_key", oauthConsumerKey);
        }
        log.info("获取userInfo请求地址：{}", userInfoUrl);
        log.info("获取userInfo请求参数：{}", JSONObject.toJSONString(m3));
        String userInfoResult = HttpUtils.createGetHttp(userInfoUrl, m3);
        log.info("获取userInfo请求响应：{}", userInfoResult);
        if(StringUtils.isEmpty(userInfoResult)){
            return Result.error(SystemCodeEnum.SYSTEM_ERROR);
        }
        JSONObject userInfoJsonObject = JSONObject.parseObject(userInfoResult);
        String userInfoRet = userInfoJsonObject.getString("ret");
        if(!"0".equals(userInfoRet)){
            return Result.error(SystemCodeEnum.SYSTEM_ERROR);
        }
        String userinfo = userInfoJsonObject.getString("userinfo");
        JSONObject user = JSONObject.parseObject(userinfo);
        String mobile = user.getString("mobile");
        //开始hr系统鉴权
        log.info("---开始hr系统鉴权并login---");
        List<AdminUserInfo> infoList = adminUserService.findByUsername(mobile).getData();
        if (infoList.isEmpty()) {
            return Result.error(AuthorizationCodeEnum.AUTHORIZATION_USER_DOES_NOT_EXIST);
        }
        UserInfo userInfo = BeanUtil.copyProperties(infoList.get(0), UserInfo.class);
        userInfo.setType(1);
        userInfo.setLoginType(1);
        log.info("hr login----->>>请求参数：{}", JSONObject.toJSONString(userInfo));
        Result<LoginVO> login = login(userInfo);
        log.info("hr login----->>>请求响应：{}", JSONObject.toJSONString(login));
        return login;
    }
}
